package com.moonshineproject.plugin.actionscript.as3parser
{
	import __AS3__.vec.Vector;
	
	import com.moonshineproject.plugin.actionscript.as3parser.completers.DotCompleter;
	import com.moonshineproject.plugin.actionscript.as3parser.completers.TypeCompleter;
	import com.moonshineproject.plugin.actionscript.as3parser.parsers.DefinitionParser;
	import com.moonshineproject.plugin.actionscript.as3parser.parsers.ImportParser;
	import com.moonshineproject.plugin.actionscript.as3parser.vo.MemberBase;
	import com.moonshineproject.plugin.actionscript.as3project.vo.AS3ProjectVO;
	import com.moonshineproject.text.vo.CompletionContext;
	
	import flash.utils.getTimer;
	
	import mx.collections.ArrayCollection;
	
	import org.as3commons.asblocks.parser.api.IParserNode;
	import org.as3commons.asblocks.parser.impl.AS3Parser;
	
	public class NodeFinder
	{
		protected static const DOT_COMPLETION:String = "dotCompletion";
		protected static const TYPE_COMPLETION:String = "typeCompletion";
		
		public var charIdx:int;
		public var lineIdx:int;
		public var caretColumnIdx:int;
		
		public var metadata:MetadataManager;
		public var completionContext:CompletionContext;
		
		protected var project:AS3ProjectVO;
		protected var parents:Array;
		
		public function NodeFinder(proj:AS3ProjectVO) 
		{
			metadata = proj.parserController.metadata;
			project = proj;
		}
		
		public function find(source:Vector.<String>, filePath:String, projectPath:String):ArrayCollection
		{	
			var now:int = flash.utils.getTimer();
			
			// Up lineIdx by one. The parser line indicies isn't 0-based
			lineIdx += 1;

			// Create parser
			var parser:AS3Parser = new AS3Parser();
			// Don't parse blocks, 'tis a bit slow & we don't really need to
			parser.parseBlocks = false;			
			// Build abstract syntax tree
			parser.buildAst(source, filePath);

			// In case parser failed/errored out we fetch the results like this
			var astRoot:IParserNode = parser.result;

			// Find imports
			var imp:ImportParser;
			imp = new ImportParser(astRoot, metadata, project.classpaths);
			imp.resolveImports();
		 			
		 			
			parents = [];
			// Walk down & find which node the cursor is on
			var node:IParserNode = recurseFind(astRoot);

			// If we are inside a block that's unparsed, go at it
			var blockDefinitions:Vector.<MemberBase>
			if (node.kind == "block")
			{
				// Parse that block only
				var blockNode:IParserNode = parser.parseBlockFragment(node);
				
				var blockDefinitionParser:DefinitionParser = new DefinitionParser(blockNode, imp);
				blockDefinitionParser.resolveBlockDefinitions();
				blockDefinitions = blockDefinitionParser.definitions;
				 
				// Walk down again, so we're sure we got the right node
				node = recurseFind(blockNode);
			}

			trace("Source parsing took: " + (flash.utils.getTimer()-now) + " ms");
			
			// The parser doesn't always return the 'correct' node for incomplete statements.
			//  use this to help find the correct context
			var lastChar:String = source[this.lineIdx-1].charAt(caretColumnIdx-1);
			
			// Completion tasks could be:
			//  'packageCompletion', 'importCompletion', 'dotCompletion', 'parameterCompletion'
			//  could also be 'overrideCompletion' for override statements
			var task:String = findCompletionTask(node, parents, lastChar);
			
			var completions:ArrayCollection;
			switch (task)
			{
				case DOT_COMPLETION:
				{	
					var dotcomplete:DotCompleter = new DotCompleter(astRoot, parents, metadata, imp);
					dotcomplete.blockDefinitions = blockDefinitions;
					completions = dotcomplete.getCompletion(node, lastChar, caretColumnIdx);
					if (dotcomplete.filterCharStart > -1)
					{
						completionContext = new CompletionContext();
						completionContext.filterCharStart = dotcomplete.filterCharStart;
						// Ignores anything after '(' so we can type drawRect(10, 10, 200, 200) and not filter it out
						completionContext.dontFilterAfterTriggerChar = "(";
					}
					break;
				}
				case TYPE_COMPLETION:
				{
					imp = new ImportParser(astRoot, metadata, project.classpaths);
					imp.resolveImports();
					
					var typecomplete:TypeCompleter = new TypeCompleter(metadata, imp);
					completions = typecomplete.getCompletions(node, lastChar, caretColumnIdx);
					completionContext = typecomplete.completionContext;
					break;
				}
				default:
				{
					trace("NodeFinder: No completion task found. Exiting.");
					return null;
				}
			}
			
			trace("Completion took: " + (flash.utils.getTimer()-now) + " ms");

			return completions;
		}
		
		// Look through AST for node that caret is in
		protected function recurseFind(node:IParserNode):IParserNode
		{
			//trace("entering " + node.kind);
			if (!node.children) return node;
			
			// Save path to the node for later lookups
			parents.push(node);
			
			var nextNodeLine:int;
			var nextNodeColumn:int;
			for (var i:int = 0; i < node.children.length; i++)
			{
				var child:IParserNode = node.children[i] as IParserNode;
					
				nextNodeLine = (i+1 < node.children.length) ? node.children[i+1].line : int.MAX_VALUE;
				nextNodeColumn = (i+1 < node.children.length) ? node.children[i+1].column : int.MAX_VALUE;

				// Don't overshoot if we're on the right line
				if (child.line == lineIdx 
					&& child.column <= caretColumnIdx 
					&& nextNodeColumn > caretColumnIdx)
				{
					return recurseFind(child);
				}
				
				// Dive as late as possible
				if (child.line <= lineIdx && nextNodeLine > lineIdx)
				{
					return recurseFind(child);
				}
			}
			
			return node;
		}

		// Figure out what kind of completion we can offer from the node+parent context
		// TODO: Add more actions
		protected function findCompletionTask(node:IParserNode, parents:Array, lastChar:String):String
		{
			var parentNode:IParserNode = parents[parents.length-1];
			
			if (node.kind == "primary" && parentNode.kind == "this")
			{
				return DOT_COMPLETION;
			}
			else if (node.kind == "primary" && parentNode.kind == "dot")
			{
				return DOT_COMPLETION;
			}
			else if (node.kind == "primary" && parentNode.kind == "arguments" && parentNode.parent.kind == "call")
			{
				return DOT_COMPLETION;
			}
			else if (node.kind == "this" && lastChar == ".")
			{
				// A line with just 'this' shouldn't trigger a completion list, 
				//  so we look for "." as well. 
				return DOT_COMPLETION;
			}
			else if (node.kind == "arguments" && node.parent.kind == "call" && node.parent.parent == "dot" && lastChar == ".")
			{
				// Matches for this.foo().
				return DOT_COMPLETION;
			}
			else if (node.kind == "name" && parentNode.kind == "name-type-init")
			{
				// Should match 'var foo:' where name node is 'foo'
				return TYPE_COMPLETION;
			}
			else if (node.kind == "type")
			{
				// Should match foo():Str
				return TYPE_COMPLETION;
			}
			else if (node.kind == "parameter-list" && parentNode.kind == "function" && lastChar == ":")
			{
				// Should match foo():
				return TYPE_COMPLETION;
			}
			else if (node.kind == "vector" && lastChar == "<")
			{
				return TYPE_COMPLETION;
			}
			else if (node.kind == "new" && lastChar == " ")
			{
				return TYPE_COMPLETION;
			}
			else if (node.kind == "primary" && node.parent.kind == "new")
			{
				return TYPE_COMPLETION;
			}
			else if (node.kind == "extends" && node.parent.kind == "class")
			{
				return TYPE_COMPLETION;
			}
			else if (node.kind == "implements" && node.parent.kind == "class")
			{
				return TYPE_COMPLETION;
			}
			else if (node.kind == "primary")
			{
				return DOT_COMPLETION;
			}
			
			return null;
		}
	}
	
}